package restdom.service.impl;

import java.beans.PropertyEditor;
import java.io.Serializable;
import java.util.List;

import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.dao.DataAccessException;

import restdom.dao.GenericDao;
import restdom.finder.FinderSupport;
import restdom.service.GenericService;
import restdom.service.ServiceException;

public class GenericServiceImpl<T, ID extends Serializable> implements
		GenericService<T, ID>, FinderSupport<T> {

	private GenericDao<T, ID> dao;

	private PropertyEditor idEditor;

	private boolean wrapException = false;

	public GenericServiceImpl(GenericDao<T, ID> dao) {
		this(dao, null, false);
	}

	public GenericServiceImpl(GenericDao<T, ID> dao, boolean wrapException) {
		this(dao, null, wrapException);
	}

	public GenericServiceImpl(GenericDao<T, ID> dao, PropertyEditor idEditor) {
		this(dao, idEditor, false);
	}

	public GenericServiceImpl(GenericDao<T, ID> dao, PropertyEditor idEditor,
			boolean wrapException) {
		if (dao == null) {
			throw new IllegalArgumentException("Generic DAO must be specified");
		}
		this.dao = dao;
		this.idEditor = idEditor;
		this.wrapException = wrapException;
	}

	public ID create(T object) {
		if (wrapException) {
			try {
				return dao.create(object);
			} catch (DataAccessException e) {
				throw new ServiceException("Failed to create object", e);
			}
		} else {
			return dao.create(object);
		}
	}

	public T read(ID id) {
		if (wrapException) {
			try {
				return dao.read(id);
			} catch (DataAccessException e) {
				throw new ServiceException("Failed to read object ["
						+ id.toString() + "]", e);
			}
		} else {
			return dao.read(id);
		}
	}

	public T readByString(String idText) {
		if (idEditor == null) {
			throw new UnsupportedOperationException("Missing property editor");
		}
		idEditor.setAsText(idText);
		@SuppressWarnings("unchecked")
		ID id = (ID) idEditor.getValue();
		return read(id);
	}

	public void delete(T object) {
		if (wrapException) {
			try {
				dao.delete(object);
			} catch (DataAccessException e) {
				throw new ServiceException("Failed to delete object", e);
			}
		} else {
			dao.delete(object);
		}
	}

	public void update(T object) {
		if (wrapException) {
			try {
				dao.update(object);
			} catch (DataAccessException e) {
				throw new ServiceException("Failed to update object", e);
			}
		} else {
			dao.update(object);
		}
	}

	public List<T> find(String finder, Object... args) {
		Class daoClass = AopUtils.getTargetClass(dao);
		if (FinderSupport.class.isAssignableFrom(daoClass)) {
			try {
				if (AopUtils.isAopProxy(dao)) {
					@SuppressWarnings("unchecked")
					FinderSupport<T> finderExecutor = (FinderSupport<T>) ((Advised) dao)
							.getTargetSource().getTarget();
					return finderExecutor.find(finder, args);
				} else {
					return ((FinderSupport<T>) dao).find(finder, args);
				}
			} catch (DataAccessException e) {
				if (wrapException) {
					throw new ServiceException("Failed to find objects ["
							+ finder + "]", e);
				} else {
					throw e;
				}
			} catch (Exception e) {
				throw new ServiceException(
						"Failed to find objects (using proxy target) ["
								+ finder + "]", e);
			}
		}
		throw new UnsupportedOperationException("Cannot delegate finder method"
				+ " to Dao, it's not a FinderExecutor.");
	}

	public void setWrapException(boolean wrapException) {
		this.wrapException = wrapException;
	}
}
